/**
 * File Gui.java 
 */
package cs.gui;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Observable;
import java.util.Observer;
import java.util.Set;

import javax.swing.DefaultListModel;
import javax.swing.ImageIcon;
import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.event.ListDataEvent;
import javax.swing.event.ListDataListener;

import cs.controller.Controller;
import cs.entity.* ;
import cs.model.FileToSendModel;
import cs.model.ReceivedFileModel;
import cs.model.ReceivedMsgModel;
import cs.model.State;
import cs.model.State.LocalUserState;
import cs.ui.FromUser;
import cs.ui.ToUser;

/**
 * Class boundary of user side. It includes all the Graphic User Interface.<br>
 * Its instantiation will lead to the creation of the child frames.<br>
 * This is a view in perspective of the MVC pattern.<br>
 * This implements Observer interface to communicate to diffuse 
 * model changes to different view
 * <br>
 * This class handles all interactions with the local user. (mouse clicks, keyboard input ...)
 *  The ActionListenner are created in the constructor.
 *  <p>
 * <ul> The two important methods of this class are:
 *  <li> update (): Processes of change model to inform the user </li>
 *  <li> ActionPerformed() :  Handles the actions of the user to inform the controller.</li></ul>
 *  <p>
 * Package  cs.gui
 * Project ChatSystemG1 - INSA 4INFOA1 - 2013/2014
 * @author Julie DUMONS & Juste OUEDRAOGO
 * @version 1.0
 */
public class ChatGui implements ActionListener,  FromUser, Observer, ListDataListener, ToUser, WindowListener, MouseListener, DocumentListener {

	/**
	 * Main program frame containing the list of users and an historic of the chat
	 */
	private static MainFrame mainFrame ; 

	/**
	 * Frame opening to launch the program to ask for username.
	 * Used in the connection phase
	 */
	private static WelcomeFrame welcomeFrame ; 

	/**
	 * Binary structure to reference the windows open dialog and link with a remote user.
	 * Each user must be present only once in this list.
	 */
	private static HashMap <User,DialogFrame> usersDialog ; 


	/**
	 * ChatController who can make changes on the model
	 */
	private static Controller controller ; 


	/**
	 * Constructor of graphic user interface, calling the manufacturers of the component windows.
	 * Add the actionListener of all buttons	
	 */
	private FileTransfert transfert ;
	public ChatGui()  {
		// new empty HashMap 
		usersDialog = new HashMap<User,DialogFrame>() ; 

		// call constructor of welcomeFrame
		welcomeFrame = new WelcomeFrame() ; 

		// call constructor of mainFrame
		mainFrame = new MainFrame() ; 

		// Adding the action listener
		addAllActionListener() ;
		
	}

	

	/**
	 * Add the actionListener of dynamic elements of GUI
	 * (Not the DialogFrame )
	 */
	private void addAllActionListener() {
		// Action listener of connect button
		welcomeFrame.getConnectButton().addActionListener(this) ; 
		welcomeFrame.getUsernameField().getDocument().addDocumentListener(this) ; 
		mainFrame.getBtnDeconnect().addActionListener(this);
		mainFrame.getBtnDiscuter().addActionListener(this); 
		mainFrame.getList().addMouseListener(this);
		mainFrame.addWindowListener(this); 

	}

	/**
	 * Handle all events of chatGUI package
	 */
	@Override
	public void actionPerformed(ActionEvent e) {

		if (e.getSource() == welcomeFrame.getConnectButton() ) {
			connect();				// Click on connect button
		} else if (e.getSource() == mainFrame.getBtnDeconnect()) {
			disconnect(); 			// Click on disconnect button
		} else if (e.getSource() == mainFrame.getBtnDiscuter()) {
			// Click on discuss button

			// test if one user is selected
			if(!(mainFrame.getList().isSelectionEmpty())) {
				// grab the user selected
				User u = (User) mainFrame.getList().getSelectedValue() ; 
				// open the dialog (or reactivate the frame if already created)
				openDialog(u); 
				// notify the user of the opening dialog
				mainFrame.notifyUser("Ouverture de conversation avec : "+u.getUsername(),'d');

			} else {
				// notify the user of the unselected item
				mainFrame.notifyUser("Veuillez selectionner un utilisateur pour pouvoir lui parler",'e');
			}
		} else {
			// Clic on Dialog Frame
			// get the users in the hashmap
			Set<User> listUsers=usersDialog.keySet(); 
			Iterator<User> iterator=listUsers.iterator();

			// for each user, test if the event comes by the dialog frame
			while(iterator.hasNext()) {
				User u = (User) iterator.next(); 
				DialogFrame du = getDialogWithUser(u) ; 
				if ( e.getSource() == du.getSendText()) {
					if (!du.getInputArea().getText().equals("")) {
						// call write message to modify the model
						writeMsg(new UserTextMessage(u,du.getInputArea().getText())); 
						// print in historic the sent message
						du.newMsg(new UserTextMessage(controller.getLocalUser(),du.getInputArea().getText() )); 
						// empty the field
						du.emptyInputArea();
					} // else do nothing because the text area is empty


				} else if (e.getSource() == du.getBtnSendFile()) {
					// Btn sent => open a File chooser and call ChooseFile
					JFileChooser chooser = new JFileChooser();
					int returnVal = chooser.showOpenDialog(null);

					if(returnVal == JFileChooser.APPROVE_OPTION) {
						chooseFile( new UserFile(u, chooser.getSelectedFile())) ; 
						System.out.println("You chose to open this file: " +
								chooser.getSelectedFile().getName());
						

					}

				}
			} 
		}
	}


	/*
	 * CONNECT / DISCONNECT 
	 * (non-Javadoc)
	 * @see cs.ui.FromUser#connect()
	 */

	/**
	 * Message received by gui and sent to the controller to notify the connect action from user
	 */
	@Override
	public void connect() {
		// TODO Auto-generated method stub
		controller.performConnect(welcomeFrame.getUsernameField().getText()) ; 
	}

	/**
	 * Message received by gui and sent to the controller to notify the disconnect action from user
	 */
	@Override
	public void disconnect() {
		// TODO Auto-generated method stub
		controller.performDisconnect() ; 

	}

	@Override
	public void username() {
		// TODO Auto-generated method stub

	}

	@Override
	public void close() {
		// TODO Auto-generated method stub

	}


	/**
	 * Methods called when the Observable Classes are changing
	 */
	@Override
	public void update(Observable o, Object arg) {

		// CHANGE OF STATE
		if (o instanceof State) {

			if ( (LocalUserState)arg == LocalUserState.ONLINE ) {
				// connection : close the Welcome window and open the main frame
				welcomeFrame.setVisible(false); 
				mainFrame.setLblInfoUsername(controller.getLocalUser().getUsername()); 
				mainFrame.setVisible(true); 
				welcomeFrame.informError(false); 


			} else if ( (LocalUserState)arg == LocalUserState.OFFLINE ) {
				// deconnection : close the mainframe and open the welcome window
				mainFrame.dispose(); 
				welcomeFrame.setVisible(true); 
				welcomeFrame.informError(false); 
				//usersDialog.clear(); 
			}else if ( (LocalUserState)arg == LocalUserState.ERROR ) {
				//Error informed by network
				mainFrame.dispose(); 
				welcomeFrame.setVisible(true); 
				welcomeFrame.informError(true); 
				//usersDialog.clear(); 
			}
		} else if (o instanceof ReceivedMsgModel) {
			newMsg(controller.getReceivedMsg()); 

		} else if (o instanceof ReceivedFileModel) {
			// Catch the update Argument : UserFile
			UserFile uf = (UserFile)arg ; 
		
			// Check the State of UserFile who has changed in the model
			if (uf.getState() == UserFile.State.REQUEST ) {
				newFile(uf); 
			} else if (uf.getState() == UserFile.State.ACCEPTED ) {
				acceptedReceivedFile(uf);
			} else if (uf.getState() == UserFile.State.REFUSED ) {
				refusedReceivedFile(uf);
			} else if (uf.getState() == UserFile.State.COMPLETED) {
				if(transfert!=null) transfert.dispose();
				completedReceivedFile(uf);
			}
		}else if (o instanceof FileToSendModel ) {
			// Catch the update Argument : UserFile
			UserFile uf = (UserFile)arg ; 

			// Check the State of UserFile who has changed in the model
			if (uf.getState() == UserFile.State.COMPLETED) {
				completedSendedFile(uf); 
			}
		}


	}




	/*
	 * USERLIST EVENTS
	 */
	@Override
	public void contentsChanged(ListDataEvent e) {		
	}

	/**
	 * Private method to compare the userList ( in the model) 
	 * with the dialog Open (in usersDialog hashmap).
	 * It is used for close dialog with disconnected users. 
	 * Called when one user is removed on the userList
	 * @return user wich is disconnected but with a dialog open
	 */
	private User checkUsersDialog() {
		// parcours de la liste des fenetres
		Set<User> listUsers=usersDialog.keySet(); 
		Iterator<User> iterator=listUsers.iterator();
		DefaultListModel ul = controller.getListUser() ; 
		User usrreturn = null ; 

		while(iterator.hasNext()) {
			User u = (User) iterator.next(); 
			// vérification 
			boolean present = true ; 

			for (int i = 0 ; !present && i< ul.size() ; i++) {
				present = ((User)(ul.get(i))).equals(u) ; 
				// utilisateur non présent dans la liste mais fenetre ouverte
			}
			if (!present) {
				usrreturn =  u ; 
			}
		}
		return usrreturn ;
	}

	@Override
	public void intervalAdded(ListDataEvent e) {
		// TODO Auto-generated method stub
		User lastAdded = (User) controller.getListUser().lastElement(); 
		mainFrame.notifyUser("Arrivée de "+lastAdded.toString(), 'h');

	}

	@Override
	public void intervalRemoved(ListDataEvent e) {
		User u = checkUsersDialog() ;
		if (!(u == null)) {
			getDialogWithUser(u).dispose();
			usersDialog.remove(u);
			mainFrame.notifyUser("Départ de "+u.toString(), 'g');
		}
	}

	@Override
	public void updateList() {
		// TODO Auto-generated method stub
	}

	/*
	 * DIALOG EVENTS
	 * (non-Javadoc)
	 * @see cs.ui.ToUser#newMsg(cs.entity.UserTextMessage)
	 */

	/**
	 * Call when a message is received
	 * Open or created a new DialogFrame Frame 
	 * @param u the userTextMessageReceived
	 */
	@Override
	public void newMsg(UserTextMessage u) { 
		//TODO : gérer le comportement peut être génant 
		// si plusieurs personnes parlent en même temps
		// created or open the dialog window
		openDialog(u.getRemoteUser()); 
		// tell the DialogFrame that a new message is arrived
		getDialogWithUser(u.getRemoteUser()).newMsg(u); 
	}


	/**
	 * Put on Front or created a new Dialog Frame
	 * @param u the user for the Dialog Frame
	 */
	public void openDialog(User u) {		
		if (dialogCreated(u) ) {
			System.out.println("deja cree"); 
			// put to front the window
			getDialogWithUser(u).toFront(); 
		} else {
			// create and add to usersDialog the DialogFrame
			usersDialog.put(u, new DialogFrame(u.getUsername())); 
			// add the gui to the action listener of this new Frame
			System.out.println("Add action Lis"); 
			// add action Listener of buttons
			DialogFrame dialogFrame = getDialogWithUser(u) ; 
			dialogFrame.addWindowListener(this); 
			dialogFrame.getSendText().addActionListener(this);
			dialogFrame.getBtnSendFile().addActionListener(this);
		}
	}

	/**
	 * Check if the remoteUser is already present in usersDialog
	 * (by adress IP and not by the username)
	 * @param remoteUser
	 * @return true if a dialog frame is already created
	 * and false if it not.
	 */
	private boolean dialogCreated(User remoteUser) {
		return usersDialog.containsKey(remoteUser); 
	}


	@Override
	public void writeMsg(UserTextMessage msg) {
		controller.performAddSendMsg(msg); 
	}

	@Override
	public void chooseFile(UserFile uf) {
		getDialogWithUser(uf.getRemoteUser()).setTransfert(new FileTransfert("Uploading "+uf.getName()+" ..."));
		controller.performAddSendFile( uf); 
	}

	/**
	 * Called when a ReceivedFile are added to the Model.
	 * <p>
	 * It request the user by a JOptionPane (YES/NO) 
	 * and changes the State of the userFile in argument
	 * depending on the user response. 
	 * @param userFile the userFile which is added to the queue
	 */
	public void newFile(UserFile userFile) {
		User u = userFile.getRemoteUser() ; 
		getDialogWithUser(u).newMsg(new UserTextMessage(
				u,"Un fichier est en attente d'acceptation"));

		int rep = JOptionPane.showConfirmDialog(getDialogWithUser(u),
				"Le fichier "+userFile.getName()+" ("+userFile.getSize()+" octets) "+
						"veut être envoyé par "
						+u+
						"\nVoulez-vous recevoir ce fichier ?",
						"Fichier en attente d'acceptation", //title
						JOptionPane.YES_NO_OPTION);

		switch (rep) {
		case JOptionPane.YES_OPTION : 
			//choose yes 
			JFileChooser chooser = new JFileChooser();
			//suggest a file to user
			chooser.setSelectedFile(userFile.getFile());
			int returnVal = chooser.showSaveDialog(null);

			if(returnVal == JFileChooser.APPROVE_OPTION) {
				userFile.setFile( chooser.getSelectedFile()) ; 
				System.out.println("The downloaded file will be writen at this emplacement :" +
						chooser.getSelectedFile().getName());
				controller.performFileAccepted(userFile); 

			} else { //CANCEL_OPTION OR ERROR_OPTION
				System.out.println("Emplacement not find, cancel the reception"); 
				controller.performFileRefused(userFile); 
			}
			break;

		case JOptionPane.NO_OPTION : 
			// LocalUser refused file
			controller.performFileRefused(userFile); 
		default : 
			// if close the dialog
			break; 

		}
	}

	/**
	 * Called when the local user have refused the entering file. 
	 * Inform the user in the dialog Frame
	 * @param uf UserFile in the model who is refused
	 */
	private void refusedReceivedFile(UserFile uf) {
		getDialogWithUser(uf.getRemoteUser()).newMsg(new UserTextMessage(controller.getLocalUser(),"Reception du fichier entrant refusée."));
	}

	/**
	 * Called when the local user have accepted the file request.
	 * It display a information of transfert progression.
	 * @param uf
	 */
	private void acceptedReceivedFile(UserFile uf) {
		//getDialogWithUser(uf.getRemoteUser()
		DialogFrame du = getDialogWithUser(uf.getRemoteUser()) ; 
		du. setTransfert(new FileTransfert("Downloading "+uf.getName()+" ..."));
		getDialogWithUser(uf.getRemoteUser()).newMsg(new UserTextMessage(controller.getLocalUser(),"Reception du fichier entrant acceptée."));

	}

	private void completedReceivedFile(UserFile uf) {
		getDialogWithUser(uf.getRemoteUser()).getTransfert().setVisible(false);
		getDialogWithUser(uf.getRemoteUser()).newMsg(new UserTextMessage(controller.getLocalUser(),"Reception du fichier entrant terminée à l'emplacement:"));
		getDialogWithUser(uf.getRemoteUser()).newMsg(new UserTextMessage(controller.getLocalUser(),uf.getFile().getPath()));
	}


	private void completedSendedFile(UserFile uf) {
		getDialogWithUser(uf.getRemoteUser()).getTransfert().setVisible(false);
		getDialogWithUser(uf.getRemoteUser()).newMsg(new UserTextMessage(controller.getLocalUser(),"Envoi du fichier terminé."));		
	}

	/*
	 *GETTERS / SETTERS
	 */

	/**
	 * Method implemented in case of the structure of userDialog changes
	 * Find the dialogFrame corresponding to the User in parameter, 
	 * if it's not created (or yet closed), create it
	 * @param remoteUser User to find in the list of usersDialog
	 * @return <li>the dialog frame who correspond to remoteUser </li>
	 * <li>null if is isn't find</li>
	 */
	private DialogFrame getDialogWithUser(User remoteUser) {
		if (!(dialogCreated(remoteUser))) {
			openDialog(remoteUser); 
		}
		return usersDialog.get(remoteUser); 
	}

	/**
	 * Find the User corresponding to the DialogFrame in parameter
	 * @param dialogFrame
	 * @return <li>the user who correspond to the dialogFrame </li>
	 * <li>null if is isn't find</li>
	 */
	private User getUserWithDialog( DialogFrame dialogFrame) {
		// get the users in the hashmap
		Set<User> listUsers=usersDialog.keySet(); 
		Iterator<User> iterator=listUsers.iterator();

		// for each user, test if the event comes by the dialog frame
		while(iterator.hasNext()) {
			User u = (User) iterator.next(); 
			if (usersDialog.get(u) == dialogFrame) {
				return u ; 
			} 

		}
		return null ; 
	}

	/**
	 * Getter of main frame
	 * @return mainFrame the Main frame
	 */
	public MainFrame getMainFrame() {
		return mainFrame;
	}

	/**
	 * Getter of the userDialog
	 * @return usersDialog
	 */
	public HashMap<User, DialogFrame> getUsersDialog() {
		return usersDialog;
	}

	/**
	 * Used once, in construction of chat system for the MVC model
	 * set the controller
	 * @param ctrl
	 */
	public void setController(Controller ctrl){
		controller = ctrl;
	}

	@Override
	public void windowActivated(WindowEvent arg0) {
		// TODO Auto-generated method stub

	}

	@Override
	public void windowClosed(WindowEvent arg0) {
		//if arg0.getComponent().ge
	}

	@Override
	public void windowClosing(WindowEvent arg0) {
		Window windowClosing = arg0.getWindow() ; 

		// One dialogFrame is closing 
		// We removed the corresponding element of the userDialog HashMap
		if (windowClosing instanceof DialogFrame ) {
			User u = getUserWithDialog((DialogFrame) windowClosing); 
			if (u != null ) {
				usersDialog.remove(u) ; 
			}				
		}
		
		if (windowClosing instanceof MainFrame ) {
			disconnect();
		}
	}

	@Override
	public void windowDeactivated(WindowEvent arg0) {
		// TODO Auto-generated method stub

	}

	@Override
	public void windowDeiconified(WindowEvent arg0) {
		// TODO Auto-generated method stub

	}

	@Override
	public void windowIconified(WindowEvent arg0) {
		// TODO Auto-generated method stub

	}

	@Override
	public void windowOpened(WindowEvent arg0) {
		// TODO Auto-generated method stub

	}

	@Override
	public void mouseClicked(MouseEvent evt) {
		int index =  mainFrame.getList().locationToIndex(evt.getPoint());
		if (evt.getSource() == mainFrame.getList() && evt.getClickCount() == 2 && index!=-1 ) {
			User u = (User) mainFrame.getList().getSelectedValue(); 
			// open the dialog (or reactivate the frame if already created)
			openDialog(u); 
			// notify the user of the opening dialog
			mainFrame.notifyUser("Ouverture de conversation avec : "+u.getUsername(),'d');
		}
	}


	@Override
	public void mouseEntered(MouseEvent arg0) {
		// TODO Auto-generated method stub

	}

	@Override
	public void mouseExited(MouseEvent arg0) {
		// TODO Auto-generated method stub

	}

	@Override
	public void mousePressed(MouseEvent arg0) {

	}

	@Override
	public void mouseReleased(MouseEvent arg0) {
		// TODO Auto-generated method stub

	}

	public void changedUpdate(DocumentEvent e) {
		if (e.getDocument() == welcomeFrame.getUsernameField().getDocument()) {
			changedUsernameField();
		}
	}


	public void removeUpdate(DocumentEvent e) {
		if (e.getDocument() == welcomeFrame.getUsernameField().getDocument()) {
			changedUsernameField();
		}	
	}
	public void insertUpdate(DocumentEvent e) {
		if (e.getDocument() == welcomeFrame.getUsernameField().getDocument()) {
			changedUsernameField();
		} 
	}

	private void changedUsernameField() {
		if (welcomeFrame.getUsernameField().getText().equals("")){
			welcomeFrame.getConnectButton().setEnabled(false);
		}
		else {
			welcomeFrame.getConnectButton().setEnabled(true);
		}

	}

}

